//Copyright (C)2002 Merkur ( merkur-@users.sourceforge.net / http://www.emule-project.net )
//
//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either
//version 2 of the License, or (at your option) any later version.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

//#include "stdafx.h"
#include "DownloadQueue.h"
#include "updownclient.h"
#include "PartFile.h"

#include <wx/filename.h>
#include <wx/listimpl.cpp>



CDownloadQueue::CDownloadQueue(CPreferences* in_prefs,CSharedFileList* in_sharedfilelist){
	app_prefs = in_prefs;
	sharedfilelist = in_sharedfilelist;
	filesrdy = 0;
	datarate = 0;
	cur_udpserver = 0;
	lastfile = 0;
	lastudpsearchtime = 0;
	lastudpstattime = 0;
	udcounter = 0;
	m_nDownDataRateMSOverhead = 0;
	m_nDownDatarateOverhead = 0;
	m_nDownDataOverheadSourceExchange = 0;
	m_nDownDataOverheadFileRequest = 0;
	m_nDownDataOverheadOther = 0;
	m_nDownDataOverheadServer = 0;
	m_nDownDataOverheadSourceExchangePackets = 0;
	m_nDownDataOverheadFileRequestPackets = 0;
	m_nDownDataOverheadOtherPackets = 0;
	m_nDownDataOverheadServerPackets = 0;
}

void CDownloadQueue::UpdateDisplayedInfo(bool force) {
    DWORD curTick = ::GetTickCount();

    if(force || curTick-m_lastRefreshedDLDisplay > MINWAIT_BEFORE_DLDISPLAY_WINDOWUPDATE+(uint32)(rand()/(RAND_MAX/1000))) {
        theApp.emuledlg->transferwnd->downloadlistctrl->UpdateItem(this);
        m_lastRefreshedDLDisplay = curTick;
    }
}

void CDownloadQueue::AddPartFilesToShare(){
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		CPartFile* cur_file = filelist.GetAt(pos);
		if (cur_file->GetStatus(true) == PS_READY) {
			sharedfilelist->SafeAddKFile(cur_file,true);
			printf("Sharing %s\n",cur_file->GetFullName());
		}
	}
}

void CDownloadQueue::CompDownDatarateOverhead(){
	m_AvarageDDRO_list.AddTail(m_nDownDataRateMSOverhead);
	if (m_AvarageDDRO_list.GetCount() > 150)
		m_AvarageDDRO_list.RemoveAt(m_AvarageDDRO_list.GetHeadPosition());
	
	m_nDownDatarateOverhead = 0;
	m_nDownDataRateMSOverhead = 0;
	for (POSITION pos = m_AvarageDDRO_list.GetHeadPosition();pos != 0;m_AvarageDDRO_list.GetNext(pos))
		m_nDownDatarateOverhead += m_AvarageDDRO_list.GetAt(pos);

	if(m_AvarageDDRO_list.GetCount() > 10)
		m_nDownDatarateOverhead = 10*m_nDownDatarateOverhead/m_AvarageDDRO_list.GetCount();
	else
		m_nDownDatarateOverhead = 0;
	return;
}

void CDownloadQueue::Init(){
	// find all part files, read & hash them if needed and store into a list
	//CFileFind ff;
	int count = 0;

	//char* searchpath = new char[strlen(app_prefs->GetTempDir())+MAX_PATH];
	wxString searchPath(app_prefs->GetTempDir());
	searchPath += "/*.part.met";
	//sprintf(searchpath,"%s\\*.part.met",app_prefs->GetTempDir());
	//delete[] searchpath;

	//check all part.met files
	//bool end = !ff.FindFile(searchPath, 0);
	printf("Initiating.. %s\n",searchPath.GetData());
	wxString fileName=::wxFindFirstFile(searchPath,wxFILE);
	//while (!end){
	while(!fileName.IsEmpty()) {  
	  //end = !ff.FindNextFile();
	  //if (ff.IsDirectory())
	  //	continue;
	  wxFileName myFileName(fileName);
	  
	  printf("Filename %s\n",myFileName.GetFullName().GetData());
	  CPartFile* toadd = new CPartFile();
	  if (toadd->LoadPartFile(app_prefs->GetTempDir(),(char*)myFileName.GetFullName().GetData())){
	    count++;
	    printf("Loaded!\n");
	    filelist.AddTail(toadd);			// to downloadqueue
	    if (toadd->GetStatus(true) == PS_READY)
	      sharedfilelist->SafeAddKFile(toadd); // part files are always shared files
	    else 
	      printf("status = %d\n",toadd->GetStatus(true));
	    theApp.emuledlg->transferwnd->downloadlistctrl->AddFile(toadd);// show in downloadwindow
	  }
	  else
	    delete toadd;
	  fileName=::wxFindNextFile();
	}
	//ff.Close();

#if 0
	//try recovering any part.met files
	searchPath += ".backup";
	end = !ff.FindFile(searchPath, 0);
	while (!end){
		end = !ff.FindNextFile();
		if (ff.IsDirectory())
			continue;
		CPartFile* toadd = new CPartFile();
		if (toadd->LoadPartFile(app_prefs->GetTempDir(),ff.GetFileName().GetBuffer())){
			toadd->SavePartFile(); // resave backup
			count++;
			filelist.AddTail(toadd);			// to downloadqueue
			if (toadd->GetStatus(true) == PS_READY)
				sharedfilelist->SafeAddKFile(toadd); // part files are always shared files
			theApp.emuledlg->transferwnd->downloadlistctrl->AddFile(toadd);// show in downloadwindow

			theApp.emuledlg->AddLogLine(false, GetResString(IDS_RECOVERED_PARTMET), toadd->GetFileName());
		}
		else {
			delete toadd;
		}
	}
	ff.Close();
#endif
#warning RECOVERY NOT IMPLEMENTED	

	if(count == 0) {
		theApp.emuledlg->AddLogLine(false,GetResString(IDS_NOPARTSFOUND));
	} else {
		theApp.emuledlg->AddLogLine(false,GetResString(IDS_FOUNDPARTS),count);
		SortByPriority();
	}
}

CDownloadQueue::~CDownloadQueue(){
//	if (!theApp.emuledlg->m_app_state== APP_STATE_SHUTINGDOWN )
//		SavePartFiles(true);	// changed by InterCeptor

	for (POSITION pos =filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		//filelist.GetAt(pos)->m_hpartfile.Flush() ; // Flush before close app [Tarod]
		//filelist.GetAt(pos)->SavePartFile();
		delete filelist.GetAt(pos);
	}

}

// [InterCeptor]
void CDownloadQueue::SavePartFiles(bool del /*= false*/) {
	for (POSITION pos =filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos))
	{	

		filelist.GetAt(pos)->m_hpartfile.Flush();
		filelist.GetAt(pos)->SavePartFile();
		if (del)
			delete filelist.GetAt(pos);
	}
}

void CDownloadQueue::AddSearchToDownload(CSearchFile* toadd){
	if (IsFileExisting(toadd->GetFileHash()))
		return;
	CPartFile* newfile = new CPartFile(toadd);
	if (newfile->GetStatus() == PS_ERROR){
		delete newfile;
		return;
	}
	AddDownload(newfile);
}

void CDownloadQueue::AddSearchToDownload(CString link){
	CPartFile* newfile = new CPartFile(link);
	if (newfile->GetStatus() == PS_ERROR){
		delete newfile;
		return;
	}
	AddDownload(newfile);
}

void CDownloadQueue::StartNextFile(){
	if( !theApp.glob_prefs->StartNextFile() )
		return;
	CPartFile*  pfile = NULL;
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		CPartFile* cur_file = filelist.GetAt(pos);
		if (cur_file->GetStatus() == PS_PAUSED){
			if (!pfile){
				pfile = cur_file;
				if (pfile->GetPriority() == PR_HIGH) break;
			}
			else{
				if (cur_file->GetPriority() > pfile->GetPriority()){
					pfile = cur_file;
					if (pfile->GetPriority() == PR_HIGH) break;
				}
			}
		}
	}
	if (pfile) pfile->ResumeFile();
}

void
CDownloadQueue::AddFileLinkToDownload(CED2KFileLink* pLink)
{
	CPartFile* newfile = new CPartFile(pLink);
	if (newfile->GetStatus() == PS_ERROR){
		delete newfile;
		newfile=NULL;
	}
	else AddDownload(newfile);
	if(pLink->HasValidSources()) {
		if (newfile) newfile->AddClientSources(pLink->SourcesList);
		else{
				CPartFile* partfile = GetFileByID((uchar*)pLink->GetHashKey());
				if (partfile) partfile->AddClientSources(pLink->SourcesList);
		}

	}
}

void CDownloadQueue::AddDownload(CPartFile* newfile) {
	// Barry - Add in paused mode if required
	if (theApp.glob_prefs->AddNewFilesPaused())
		newfile->PauseFile();

	filelist.AddTail(newfile);
	SortByPriority();
	theApp.emuledlg->transferwnd->downloadlistctrl->AddFile(newfile);
	theApp.emuledlg->AddLogLine(true,GetResString(IDS_NEWDOWNLOAD),newfile->GetFileName());
	CString msgTemp;
	msgTemp.Format(GetResString(IDS_NEWDOWNLOAD)+"\n",newfile->GetFileName());
	theApp.emuledlg->ShowNotifier(msgTemp, TBN_DLOAD);
}

bool CDownloadQueue::IsFileExisting(uchar* fileid){
	if (CKnownFile* file = sharedfilelist->GetFileByID((uchar*)fileid)){
		if (file->IsPartFile())
			theApp.emuledlg->AddLogLine(true, GetResString(IDS_ERR_ALREADY_DOWNLOADING), file->GetFileName() );
		else
			theApp.emuledlg->AddLogLine(true, GetResString(IDS_ERR_ALREADY_DOWNLOADED), file->GetFileName() );
		return true;
	}
	else if ( file = this->GetFileByID((uchar*)fileid)){
		theApp.emuledlg->AddLogLine(true, GetResString(IDS_ERR_ALREADY_DOWNLOADING), file->GetFileName() );
		return true;
	}
	return false;
}


void CDownloadQueue::Process(){
	uint32 downspeed = 0;
	if (app_prefs->GetMaxDownload() != UNLIMITED && datarate > 1500){
		downspeed = (app_prefs->GetMaxDownload()*1024*100)/(datarate+1); //(uint16)((float)((float)(app_prefs->GetMaxDownload()*1024)/(datarate+1)) * 100);
		if (downspeed < 50)
			downspeed = 50;
		else if (downspeed > 200)
			downspeed = 200;
		//if (app_prefs->GetMaxDownload()*1024 < datarate)
		//	downspeed = 0xFFF;
	}
	datarate = 0;
	for (POSITION pos =filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		CPartFile* cur_file =  filelist.GetAt(pos);
		if ((cur_file->GetStatus() == PS_READY || cur_file->GetStatus() == PS_EMPTY) && cur_file->GetPriority() == PR_HIGH){
			datarate += cur_file->Process(downspeed);
		}
	}
	for (POSITION pos =filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		CPartFile* cur_file =  filelist.GetAt(pos);
		if ((cur_file->GetStatus() == PS_READY || cur_file->GetStatus() == PS_EMPTY) && cur_file->GetPriority() == PR_NORMAL){
			datarate += cur_file->Process(downspeed);
		}
	}
	for (POSITION pos =filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		CPartFile* cur_file =  filelist.GetAt(pos);
		if ((cur_file->GetStatus() == PS_READY || cur_file->GetStatus() == PS_EMPTY) && (cur_file->GetPriority() == PR_LOW || cur_file->GetPriority() == PR_AUTO )){
			datarate += cur_file->Process(downspeed);
		}
	}
	udcounter++;
	if (udcounter == 5){
		if((!lastudpstattime) || (::GetTickCount() - lastudpstattime) > UDPSERVERSTATTIME){
			lastudpstattime = ::GetTickCount();
			theApp.serverlist->ServerStats();
		}
	}
	if (udcounter == 10){
		udcounter = 0;
		if ((!lastudpsearchtime) || (::GetTickCount() - lastudpsearchtime) > UDPSERVERREASKTIME)
			SendNextUDPPacket();
	}
}

CPartFile*	CDownloadQueue::GetFileByID(uchar* filehash){
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		if (!memcmp(filehash,filelist.GetAt(pos)->GetFileHash(),16))
			return filelist.GetAt(pos);
	}
	return 0;
}

CPartFile*      CDownloadQueue::GetFileByIndex(int index){
//      if (index>=filelist.GetCount()) return 0;
        int count=0;
 
        for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
                if (count==index) return filelist.GetAt(pos);
                count++;
        }
        return 0;
}

bool CDownloadQueue::IsPartFile(void* totest){
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos))
		if (totest == filelist.GetAt(pos))
			return true;
	return false;
}

void CDownloadQueue::CheckAndAddSource(CPartFile* sender,CUpDownClient* source){
	// if we block loopbacks at this point it should prevent us from connecting to ourself
	if(!source->HasLowID() && (source->GetUserID() & 0xFF) == 0x7F) {
		delete source;
		return;
	}

	// uses this only for temp. clients
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		CPartFile* cur_file = filelist.GetAt(pos);
		for (int sl=0;sl<SOURCESSLOTS;sl++) if (!cur_file->srclists[sl].IsEmpty())
		for (POSITION pos2 = cur_file->srclists[sl].GetHeadPosition();pos2 != 0; cur_file->srclists[sl].GetNext(pos2)){
			if (cur_file->srclists[sl].GetAt(pos2)->Compare(source)){
				if (cur_file == sender){ // this file has already this source
					delete source;
					return;
				}
				// set request for this source
				if (cur_file->srclists[sl].GetAt(pos2)->AddRequestForAnotherFile(sender)){
					// add it to uploadlistctrl
					theApp.emuledlg->transferwnd->downloadlistctrl->AddSource(sender,cur_file->srclists[sl].GetAt(pos2),true);
					delete source;
					return;
				}
				else{
					delete source;
					return;
				}
			}
		}
	}
	//our new source is real new but maybe it is already uploading to us?
	//if yes the known client will be attached to the var "source"
	//and the old sourceclient will be deleted
	if (theApp.clientlist->AttachToAlreadyKnown(&source,0))
		source->reqfile = sender;
	else
		theApp.clientlist->AddClient(source,true);
	
	sender->srclists[source->sourcesslot].AddTail(source);
	theApp.emuledlg->transferwnd->downloadlistctrl->AddSource(sender,source,false);
	//theApp.emuledlg->transferwnd.downloadlistctrl.UpdateItem(this);
	UpdateDisplayedInfo();
}

void CDownloadQueue::CheckAndAddKnownSource(CPartFile* sender,CUpDownClient* source){
	if(!source->HasLowID() && (source->GetUserID() & 0xFF) == 0x7F)
		return;

	// use this for client which are already know (downloading for example)
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		CPartFile* cur_file = filelist.GetAt(pos);
		for (int sl=0;sl<SOURCESSLOTS;sl++) if (!cur_file->srclists[sl].IsEmpty())
		if (cur_file->srclists[sl].Find(source)){
			if (cur_file == sender)
				return;
			if (source->AddRequestForAnotherFile(sender))
				theApp.emuledlg->transferwnd->downloadlistctrl->AddSource(sender,source,true);
			return;
		}
	}
	source->reqfile = sender;
	sender->srclists[source->sourcesslot].AddTail(source);
	theApp.emuledlg->transferwnd->downloadlistctrl->AddSource(sender,source,false);
	UpdateDisplayedInfo();
}

bool CDownloadQueue::RemoveSource(CUpDownClient* toremove, bool	updatewindow){
	bool removed = false;
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		CPartFile* cur_file = filelist.GetAt(pos);
		for (int sl=0;sl<SOURCESSLOTS;sl++) if (!cur_file->srclists[sl].IsEmpty())
		for (POSITION pos2 = cur_file->srclists[sl].GetHeadPosition();pos2 != 0; cur_file->srclists[sl].GetNext(pos2)){
			if (toremove == cur_file->srclists[sl].GetAt(pos2)){
				cur_file->srclists[sl].RemoveAt(pos2);
				cur_file->NewSrcPartsInfo();
				removed = true;
				break;
			}
		}
		cur_file->UpdateAvailablePartsCount();
	}
	if (updatewindow){
		toremove->SetDownloadState(DS_NONE);
		theApp.emuledlg->transferwnd->downloadlistctrl->RemoveSource(toremove,0);
	}
	toremove->reqfile = 0;
	return removed;
}

void CDownloadQueue::RemoveFile(CPartFile* toremove){
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		if (toremove == filelist.GetAt(pos)){
			filelist.RemoveAt(pos);
			return;
		}
	}
}

void CDownloadQueue::DeleteAll(){
	POSITION pos;
	for (pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		CPartFile* cur_file = filelist.GetAt(pos);
		for (int sl=0;sl<SOURCESSLOTS;sl++) if (!cur_file->srclists[sl].IsEmpty())
			cur_file->srclists[sl].RemoveAll();
		// Barry - Should also remove all requested blocks
		// Don't worry about deleting the blocks, that gets handled 
		// when CUpDownClient is deleted in CClientList::DeleteAll()
		cur_file->RemoveAllRequestedBlocks();
	}
}

bool CDownloadQueue::SendNextUDPPacket(){
	if (filelist.IsEmpty() || !theApp.serverconnect->IsConnected())
		return false;
	if (!cur_udpserver)
		if (!(cur_udpserver = theApp.serverlist->GetNextServer(cur_udpserver))){
			StopUDPRequests();
		};

	// get nextfile
	CPartFile* nextfile = 0;
	while (!(nextfile && (nextfile->GetStatus() == PS_READY ||nextfile->GetStatus() == PS_EMPTY))){
		if (lastfile == 0){
			nextfile = filelist.GetHead();
			lastfile = nextfile;
		}
		else{
			POSITION pos = filelist.Find(lastfile);
			if (!pos){
				nextfile = filelist.GetHead();
				lastfile = nextfile;
			}
			else{
				filelist.GetNext(pos);
				if (pos == 0){
					cur_udpserver = theApp.serverlist->GetNextServer(cur_udpserver);
					if (cur_udpserver == 0){
						lastudpsearchtime = ::GetTickCount();
						lastfile = 0;
						return false; // finished (processed all file & all servers)
					}
					nextfile = filelist.GetHead();
					lastfile = nextfile;
				}
				else{
					nextfile = filelist.GetAt(pos);
					lastfile = nextfile;
				}
			}
		}
	}
	if( (theApp.glob_prefs->GetMaxSourcePerFileUDP()) < nextfile->GetSourceCount()) //<<--
		return true; 
	Packet packet(OP_GLOBGETSOURCES,16);
	memcpy(packet.pBuffer,nextfile->GetFileHash(),16);
	if (cur_udpserver != theApp.serverlist->GetServerByAddress(theApp.serverconnect->GetCurrentServer()->GetAddress(),theApp.serverconnect->GetCurrentServer()->GetPort()))
		theApp.serverconnect->SendUDPPacket(&packet,cur_udpserver,false);
	return true;
}

void CDownloadQueue::StopUDPRequests(){
	cur_udpserver = 0;
	lastudpsearchtime = ::GetTickCount();
	lastfile = 0;
}

void CDownloadQueue::SortByPriority(){
   POSITION pos1, pos2;
   uint16 i = 0;
   for( pos1 = filelist.GetHeadPosition(); ( pos2 = pos1 ) != NULL; ){
	   filelist.GetNext(pos1);
	   CPartFile* cur_file = filelist.GetAt(pos2);
	   if (cur_file->GetPriority() == PR_HIGH){
		   filelist.AddHead(cur_file);
		   filelist.RemoveAt(pos2);
	   }
	   else if (cur_file->GetPriority() == PR_LOW){
		   filelist.AddTail(cur_file);
		   filelist.RemoveAt(pos2);
	   }
	   i++;
	   if (i == filelist.GetCount())
		   break;
   }
}

void CDownloadQueue::GetDownloadStats(int results[]) {
	
	results[0]=0;
	results[1]=0;

	for (POSITION pos =theApp.downloadqueue->filelist.GetHeadPosition();pos != 0;theApp.downloadqueue->filelist.GetNext(pos)){
		CPartFile* cur_file =  filelist.GetAt(pos);
		results[0]+=cur_file->GetSourceCount();
		results[1]+=cur_file->GetTransferingSrcCount();
	}
}

CUpDownClient* CDownloadQueue::GetDownloadClientByIP(uint32 dwIP){
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		CPartFile* cur_file = filelist.GetAt(pos);
		for (int sl=0;sl<SOURCESSLOTS;sl++) if (!cur_file->srclists[sl].IsEmpty())
		for (POSITION pos2 = cur_file->srclists[sl].GetHeadPosition();pos2 != 0; cur_file->srclists[sl].GetNext(pos2)){
			if (dwIP == cur_file->srclists[sl].GetAt(pos2)->GetIP()){
				return cur_file->srclists[sl].GetAt(pos2);
			}
		}
	}
	return NULL;
}
